// ---------------
// PDFjs namespace
// ---------------

import {
  Component,
  Context,
  // DependencyList,
  // EffectCallback,
  HTMLAttributes,
  MutableRefObject,
  ReactElement,
  ReactNode,
  RefObject,
} from "react";

export declare namespace PdfJs {
  // Worker
  const GlobalWorkerOptions: GlobalWorker;
  interface GlobalWorker {
    workerSrc: string;
  }

  interface PDFWorkerConstructorParams {
    name: string;
  }
  class PDFWorker {
    destroyed: boolean;
    constructor(params: PDFWorkerConstructorParams);
    destroy(): void;
  }

  // Loading task
  const PasswordResponses: PasswordResponsesValue;
  interface PasswordResponsesValue {
    NEED_PASSWORD: string;
    INCORRECT_PASSWORD: string;
  }

  type VerifyPassword = (password: string) => void;
  type FileData = string | Uint8Array;

  interface LoadingTaskProgress {
    loaded: number;
    total: number;
  }

  interface LoadingTask {
    onPassword: (verifyPassword: VerifyPassword, reason: string) => void;
    onProgress: (progress: LoadingTaskProgress) => void;
    promise: Promise<PdfDocument>;
    destroy(): void;
  }
  interface PdfDocument {
    numPages: number;
    getAttachments(): Promise<{ [filename: string]: Attachment }>;
    getDestination(dest: string): Promise<OutlineDestination>;
    getDownloadInfo(): Promise<{ length: number }>;
    getMetadata(): Promise<MetaData>;
    getOutline(): Promise<Outline[]>;
    getPage(pageIndex: number): Promise<Page>;
    getPageIndex(ref: OutlineRef): Promise<number>;
  }
  interface GetDocumentParams {
    data?: FileData;
    cMapUrl?: string;
    cMapPacked?: boolean;
    httpHeaders?: Record<string, string | string[]>;
    url?: string;
    withCredentials?: boolean;
    worker?: PDFWorker;
  }
  function getDocument(params: GetDocumentParams): LoadingTask;

  // Attachment
  interface Attachment {
    content: Uint8Array;
    filename: string;
  }

  // Metadata
  interface MetaData {
    contentDispositionFilename?: string;
    info: MetaDataInfo;
  }
  interface MetaDataInfo {
    Author: string;
    CreationDate: string;
    Creator: string;
    Keywords: string;
    ModDate: string;
    PDFFormatVersion: string;
    Producer: string;
    Subject: string;
    Title: string;
  }

  // Outline
  type OutlineDestinationType = string | OutlineDestination;
  interface Outline {
    bold?: boolean;
    color?: number[];
    dest?: OutlineDestinationType;
    italic?: boolean;
    items: Outline[];
    newWindow?: boolean;
    title: string;
    unsafeUrl?: string;
    url?: string;
  }
  type OutlineDestination = [
    // The first item is used to indicate the destination page
    OutlineRef | number,
    OutlineDestinationName,
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    ...any[]
  ];
  interface OutlineDestinationName {
    name: string; // Can be 'WYZ', 'Fit', ...
  }
  interface OutlineRef {
    gen: number;
    num: number;
  }

  // View port
  interface ViewPortParams {
    rotation?: number;
    scale: number;
  }
  interface ViewPortCloneParams {
    dontFlip: boolean;
  }
  interface ViewPort {
    height: number;
    rotation: number;
    transform: number[];
    width: number;
    clone(params: ViewPortCloneParams): ViewPort;
  }

  // Render task
  interface PageRenderTask {
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    promise: Promise<any>;
    cancel(): void;
  }

  // Render SVG
  interface SVGGraphics {
    getSVG(
      operatorList: PageOperatorList,
      viewport: ViewPort
    ): Promise<SVGElement>;
  }
  interface SVGGraphicsConstructor {
    new (commonObjs: PageCommonObjects, objs: PageObjects): SVGGraphics;
  }
  let SVGGraphics: SVGGraphicsConstructor;

  // Render text layer
  interface RenderTextLayerParams {
    textContent: PageTextContent;
    container: HTMLDivElement;
    viewport: ViewPort;
    enhanceTextSelection?: boolean;
  }
  interface PageTextContent {
    items: PageTextItem[];
  }
  interface PageTextItem {
    str: string;
  }
  function renderTextLayer(params: RenderTextLayerParams): PageRenderTask;

  // Annotations layer
  interface AnnotationsParams {
    // Can be 'display' or 'print'
    intent: string;
  }
  interface AnnotationPoint {
    x: number;
    y: number;
  }
  interface Annotation {
    annotationType: number;
    color?: Uint8ClampedArray;
    dest: string;
    hasAppearance: boolean;
    id: string;
    rect: number[];
    subtype: string;
    // Border style
    borderStyle: {
      dashArray: number[];
      horizontalCornerRadius: number;
      style: number;
      verticalCornerRadius: number;
      width: number;
    };
    // For annotation that has a popup
    hasPopup?: boolean;
    contents?: string;
    modificationDate?: string;
    quadPoints?: AnnotationPoint[][];
    title?: string;
    // Parent annotation
    parentId?: string;
    parentType?: string;
    // File attachment annotation
    file?: Attachment;
    // Ink annotation
    inkLists?: AnnotationPoint[][];
    // Line annotation
    lineCoordinates: number[];
    // Link annotation
    // `action` can be `FirstPage`, `PrevPage`, `NextPage`, `LastPage`, `GoBack`, `GoForward`
    action?: string;
    url?: string;
    newWindow?: boolean;
    // Polyline annotation
    vertices?: AnnotationPoint[];
    // Text annotation
    name?: string;
  }
  const AnnotationLayer: PdfAnnotationLayer;
  interface RenderAnnotationLayerParams {
    annotations: Annotation[];
    div: HTMLDivElement | null;
    linkService: LinkService;
    page: Page;
    viewport: ViewPort;
  }
  interface PdfAnnotationLayer {
    render(params: RenderAnnotationLayerParams): void;
    update(params: RenderAnnotationLayerParams): void;
  }

  // Link service
  interface LinkService {
    externalLinkTarget?: number | null;
    getDestinationHash(dest: OutlineDestinationType): string;
    navigateTo(dest: OutlineDestinationType): void;
  }

  // Render page
  interface PageRenderParams {
    canvasContext: CanvasRenderingContext2D;
    // Should be 'print' when printing
    intent?: string;
    transform?: number[];
    viewport: ViewPort;
  }
  interface Page {
    getAnnotations(params: AnnotationsParams): Promise<Annotation[]>;
    getTextContent(): Promise<PageTextContent>;
    getViewport(params: ViewPortParams): ViewPort;
    render(params: PageRenderParams): PageRenderTask;
    getOperatorList(): Promise<PageOperatorList>;
    commonObjs: PageCommonObjects;
    objs: PageObjects;
    view: number[];
  }

  /* eslint-disable @typescript-eslint/no-empty-interface */
  interface PageCommonObjects {}
  interface PageObjects {}
  interface PageOperatorList {}
}

// --------------------
// Interfaces and types
// --------------------

// Localization provider
export interface LocalizationMap {
  [key: string]: LocalizationMap;
}

export type LocalizationContext = Context<LocalizationMap>;

export type SetLocalization = (l10n: LocalizationMap) => void;

export interface LocalizationProviderProps {
  children: (setLocalization: SetLocalization) => ReactElement;
  localization?: LocalizationMap;
}

export class LocalizationProvider extends Component<LocalizationProviderProps> {}

// Theme provider
export interface ThemeContextProps {
  currentTheme: string;
  setCurrentTheme: (theme: string) => void;
}
export type ThemeContext = Context<ThemeContextProps>;

export interface ThemeProviderProps {
  theme?: string;
}

export class ThemeProvider extends Component<ThemeProviderProps> {}

export interface Offset {
  left: number;
  top: number;
}

export enum Position {
  TopLeft = "TOP_LEFT",
  TopCenter = "TOP_CENTER",
  TopRight = "TOP_RIGHT",
  RightTop = "RIGHT_TOP",
  RightCenter = "RIGHT_CENTER",
  RightBottom = "RIGHT_BOTTOM",
  BottomLeft = "BOTTOM_LEFT",
  BottomCenter = "BOTTOM_CENTER",
  BottomRight = "BOTTOM_RIGHT",
  LeftTop = "LEFT_TOP",
  LeftCenter = "LEFT_CENTER",
  LeftBottom = "LEFT_BOTTOM",
}

export interface SlotAttr extends HTMLAttributes<HTMLDivElement> {
  ref?: MutableRefObject<HTMLDivElement | null>;
}
export interface Slot {
  attrs?: SlotAttr;
  children?: ReactNode;
  subSlot?: Slot;
}

export enum ToggleStatus {
  Close = "Close",
  Open = "Open",
  Toggle = "Toggle",
}
export type Toggle = (status?: ToggleStatus) => void;

export enum SpecialZoomLevel {
  ActualSize = "ActualSize",
  PageFit = "PageFit",
  PageWidth = "PageWidth",
}

export enum AnnotationType {
  Text = 1,
  Link = 2,
  FreeText = 3,
  Line = 4,
  Square = 5,
  Circle = 6,
  Polygon = 7,
  Polyline = 8,
  Highlight = 9,
  Underline = 10,
  Squiggly = 11,
  StrikeOut = 12,
  Stamp = 13,
  Caret = 14,
  Ink = 15,
  Popup = 16,
  FileAttachment = 17,
}

// ----------
// Components
// ----------

// Minimal button
export interface MinimalButtonProps {
  isDisabled?: boolean;
  isSelected?: boolean;
  onClick(): void;
}

export class MinimalButton extends Component<MinimalButtonProps> {}

// Normal button
export interface ButtonProps {
  onClick(): void;
}

export class Button extends Component<ButtonProps> {}

// Primary button
export interface PrimaryButtonProps {
  onClick(): void;
}

export class PrimaryButton extends Component<PrimaryButtonProps> {}

// Icon
export interface IconProps {
  size?: number;
}

export class Icon extends Component<IconProps> {}

// Menu
export class Menu extends Component {}

// MenuDivider
export class MenuDivider extends Component {}

// MenuItem
export interface MenuItemProps {
  checked?: boolean;
  icon?: ReactElement;
  isDisabled?: boolean;
  onClick(): void;
}

export class MenuItem extends Component<MenuItemProps> {}

// Modal
export interface ModalProps {
  closeOnClickOutside: boolean;
  closeOnEscape: boolean;
  content: RenderContent;
  target: RenderTarget;
}

export class Modal extends Component<ModalProps> {}

// Popover
export interface PopoverProps {
  closeOnClickOutside: boolean;
  closeOnEscape: boolean;
  content: RenderContent;
  offset: Offset;
  position: Position;
  target: RenderTarget;
}

export class Popover extends Component<PopoverProps> {}

// Portal
export type RenderContent = (toggle: Toggle) => ReactNode;
export type RenderTarget = (toggle: Toggle, opened: boolean) => ReactNode;

// Progress bar
export interface ProgressBarProps {
  progress: number;
}

export class ProgressBar extends Component<ProgressBarProps> {}

// Separator
export class Separator extends Component {}

// Spinner
export class Spinner extends Component {}

// Textbox
export interface TextBoxProps {
  placeholder?: string;
  type?: string;
  value?: string;
  onChange: (value: string) => void;
  onKeyDown?: (e: KeyboardEvent) => void;
}
export class TextBox extends Component<TextBoxProps> {}

// Tooltip
export type RenderTooltipContent = () => ReactNode;

export interface TooltipProps {
  content: RenderTooltipContent;
  offset: Offset;
  position: Position;
  target: ReactElement;
}

export class Tooltip extends Component<TooltipProps> {}

// Viewer

export interface VisibilityChanged {
  isVisible: boolean;
  ratio: number;
}

export interface RenderViewer {
  containerRef: RefObject<HTMLDivElement>;
  doc: PdfJs.PdfDocument;
  pageHeight: number;
  pageWidth: number;
  rotation: number;
  slot: Slot;
  openFile(file: File): void;
  // Jump to given page
  // `page` is zero-index based
  jumpToPage(page: number): void;
  rotate(degree: number): void;
  zoom(level: number | SpecialZoomLevel): void;
}

// Represents the error in case the document can't be loaded
export interface LoadError {
  message?: string;
  // Some possible values for `name` are
  // - AbortException
  // - FormatError
  // - InvalidPDFException
  // - MissingPDFException
  // - PasswordException
  // - UnexpectedResponseException
  // - UnknownErrorException
  name?: string;
}
export type RenderError = (error: LoadError) => ReactElement;

export interface RenderPageProps {
  annotationLayer: Slot;
  canvasLayer: Slot;
  doc: PdfJs.PdfDocument;
  height: number;
  pageIndex: number;
  rotation: number;
  scale: number;
  svgLayer: Slot;
  textLayer: Slot;
  width: number;
}
export type RenderPage = (props: RenderPageProps) => ReactElement;

// ------
// Events
// ------

// Invoked when the document is loaded successfully
export interface DocumentLoadEvent {
  doc: PdfJs.PdfDocument;
}

// Invoked when users change the current page
export interface PageChangeEvent {
  // zero-based page index
  currentPage: number;
  doc: PdfJs.PdfDocument;
}

// Invoked when users zoom the document
export interface ZoomEvent {
  doc: PdfJs.PdfDocument;
  scale: number;
}

// -------
// Plugins
// -------

export interface OpenFile {
  data: PdfJs.FileData;
  name: string;
}

export interface ViewerState {
  // The current opened file. It can be changed from outside, such as user drags and drops an external file
  // or user opens a file from toolbar
  file: OpenFile;
  // The current page index
  pageIndex: number;
  // Size of page
  pageHeight: number;
  pageWidth: number;
  rotation: number;
  // The current zoom level
  scale: number;
}

export interface PluginFunctions {
  getPageElement(pageIndex: number): HTMLElement | undefined;
  getPagesContainer(): HTMLElement | null;
  getViewerState(): ViewerState;
  jumpToDestination(
    pageIndex: number,
    bottomOffset: number,
    leftOffset: number,
    scaleTo: number | SpecialZoomLevel
  ): void;
  jumpToPage(pageIndex: number): void;
  openFile(file: File): void;
  rotate(rotation: number): void;
  setViewerState(viewerState: ViewerState): void;
  zoom(scale: number | SpecialZoomLevel): void;
}

export interface PluginOnDocumentLoad {
  doc: PdfJs.PdfDocument;
}

export enum LayerRenderStatus {
  PreRender,
  DidRender,
}

export interface PluginOnTextLayerRender {
  ele: HTMLElement;
  pageIndex: number;
  scale: number;
  status: LayerRenderStatus;
}

export interface PluginOnAnnotationLayerRender {
  annotations: PdfJs.Annotation[];
  container: HTMLElement;
  pageIndex: number;
  scale: number;
  rotation: number;
}

// Invoked when the canvas layer is rendered
export interface PluginOnCanvasLayerRender {
  ele: HTMLCanvasElement;
  pageIndex: number;
  rotation: number;
  scale: number;
  status: LayerRenderStatus;
}

export interface PluginRenderPageLayer {
  doc: PdfJs.PdfDocument;
  height: number;
  pageIndex: number;
  rotation: number;
  scale: number;
  width: number;
}

export interface Plugin {
  install?(pluginFunctions: PluginFunctions): void;
  renderPageLayer?(props: PluginRenderPageLayer): ReactElement;
  renderViewer?(props: RenderViewer): Slot;
  uninstall?(pluginFunctions: PluginFunctions): void;
  onAnnotationLayerRender?(props: PluginOnAnnotationLayerRender): void;
  onCanvasLayerRender?(props: PluginOnCanvasLayerRender): void;
  onDocumentLoad?(props: PluginOnDocumentLoad): void;
  onTextLayerRender?(props: PluginOnTextLayerRender): void;
  onViewerStateChange?(viewerState: ViewerState): ViewerState;
}

// -----
// Store
// -----

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type StoreState = Record<string, any>;

export type StoreKey<T extends StoreState> = string & keyof T;
export type StoreHandler<T> = (params: T) => void;

export interface Store<T extends StoreState> {
  subscribe<K extends StoreKey<T>>(
    eventName: K,
    handler: StoreHandler<NonNullable<T[K]>>
  ): void;
  unsubscribe<K extends StoreKey<T>>(
    eventName: K,
    handler: StoreHandler<NonNullable<T[K]>>
  ): void;
  update<K extends StoreKey<T>>(eventName: K, params: T[K]): void;
  get<K extends StoreKey<T>>(eventName: K): T[K] | undefined;
}

// The character maps that can be downloaded from
// https://github.com/mozilla/pdfjs-dist/tree/master/cmaps
export interface CharacterMap {
  isCompressed: boolean;
  url: string;
}

export interface ViewerProps {
  characterMap?: CharacterMap;
  // The default zoom level
  // If it's not set, the initial zoom level will be calculated based on the dimesion of page and the container width
  // So that, the document will fit best within the container
  defaultScale?: number | SpecialZoomLevel;
  fileUrl: string | Uint8Array;
  // Additional authentication headers
  httpHeaders?: Record<string, string | string[]>;
  // The page (zero-index based) that will be displayed initially
  initialPage?: number;
  // Plugins
  plugins?: Plugin[];
  localization?: LocalizationMap;
  renderError?: RenderError;
  renderPage?: RenderPage;
  renderLoader?(percentages: number): ReactElement;
  // Theme
  theme?: string;
  transformGetDocumentParams?(
    options: PdfJs.GetDocumentParams
  ): PdfJs.GetDocumentParams;
  // Indicate the cross-site requests should be made with credentials such as cookie and authorization headers.
  // The default value is `false`
  withCredentials?: boolean;
  onDocumentLoad?(e: DocumentLoadEvent): void;
  onPageChange?(e: PageChangeEvent): void;
  onZoom?(e: ZoomEvent): void;
}
